// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase.html#sect_protocol_connection_phase_initial_handshake
// HandshakeResponse:

use crate::{
    client::*,
    nativs::StringSelfDataType,
    protocol::{Int1, Int2, NativePacketPayload},
};

// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_ok_packet.html#sect_protocol_basic_ok_packet_sessinfo
#[derive(Default, Debug)]
pub struct SessionStateChanges {
    // int<1>
    // type of data. See enum_session_state_type
    typ: Int1,
    // data of the changed session info
    data: String,
}

impl SessionStateChanges {
    pub fn new(typ: Int1, data: String) -> Self {
        SessionStateChanges { typ, data }
    }
}

// OK_Packet
// An OK packet is sent from the server to the client to signal successful completion of a command.
// As of MySQL 5.7.5, OK packets are also used to indicate EOF, and EOF packets are deprecated.
// if CLIENT_PROTOCOL_41 is set, the packet contains a warning count.
// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_ok_packet.html
#[derive(Default, Debug)]
pub struct OKPacket {
    inner: NativePacketPayload,

    // 0x00 or 0xFE the OK packet header
    // int<1>
    header: Int1,
    // int<lenenc>
    affected_rows: usize,
    // int<lenenc>
    last_insert_id: usize,

    status_flags: Int2,
    // if capabilities & CLIENT_PROTOCOL_41 {
    // number of warnings
    warnings: Option<Int2>,
    // } else if capabilities & CLIENT_TRANSACTIONS {
    // status_flags: Int2,

    // if capabilities & CLIENT_SESSION_TRACK
    // human readable status information
    info: String,

    // if status_flags & SERVER_SESSION_STATE_CHANGED {
    session_state_changes: Option<SessionStateChanges>,
}

impl OKPacket {
    pub fn new(inner: NativePacketPayload) -> Self {
        OKPacket {
            inner,
            header: 0x00,
            affected_rows: 0,
            last_insert_id: 0,
            status_flags: 0,
            warnings: None,
            info: String::new(),
            session_state_changes: None,
        }
    }

    pub fn handle(&mut self, capability_flags: usize) {
        // affected rows
        self.affected_rows = self
            .inner
            .read_int(crate::nativs::IntegerDataType::INT_LENENC);
        // last insert-id
        self.last_insert_id = self
            .inner
            .read_int(crate::nativs::IntegerDataType::INT_LENENC);

        let mut status_flags = 0;
        if (capability_flags & CLIENT_PROTOCOL_41) != 0 {
            status_flags = self.inner.read_int(crate::nativs::IntegerDataType::INT2);
            self.warnings = Some(self.inner.read_int(crate::nativs::IntegerDataType::INT2));
        } else if (capability_flags & CLIENT_TRANSACTIONS) != 0 {
            status_flags = self.inner.read_int(crate::nativs::IntegerDataType::INT2);
        }

        if (capability_flags & CLIENT_SESSION_TRACK) != 0 {
            self.info =
                String::from_utf8_lossy(&self.inner.read_bytes(StringSelfDataType::STRING_LENENC))
                    .to_string();

            if (status_flags & SERVER_SESSION_STATE_CHANGED) != 0 {
                let typ = self.inner.read_int(crate::nativs::IntegerDataType::INT1);
                let data = String::from_utf8_lossy(
                    &self.inner.read_bytes(StringSelfDataType::STRING_LENENC),
                )
                .to_string();
                self.session_state_changes = Some(SessionStateChanges::new(typ, data));
            }
        } else {
            self.info =
                String::from_utf8_lossy(&self.inner.read_bytes(StringSelfDataType::STRING_EOF))
                    .to_string();
        }

        self.status_flags = status_flags;
    }
}

/*
The EOF_Packet packet may appear in places where a Protocol::LengthEncodedInteger may appear.
You must check whether the packet length is less than 9 to make sure that it is a EOF_Packet packet.
 is deprecated as of MySQL 5.7.5.
*/
#[derive(Default, Debug)]
pub struct EOFPacket {
    inner: NativePacketPayload,

    // 0xFE
    // int<1>
    header: Int1,
    // int<2>
    warnings: Int2,
    // int<2>
    status_flags: Int2,
}

impl EOFPacket {
    pub fn new(inner: NativePacketPayload) -> Self {
        EOFPacket {
            inner,
            header: 0xFE,
            warnings: 0,
            status_flags: 0,
        }
    }

    pub fn handle(&mut self, capability_flags: usize) {
        // from => NativePacketPayload::leader()
        // self.header = self.inner.read_int(crate::nativs::IntegerDataType::INT1);

        if (capability_flags & CLIENT_PROTOCOL_41) != 0 {
            self.warnings = self.inner.read_int(crate::nativs::IntegerDataType::INT2);
            self.status_flags = self.inner.read_int(crate::nativs::IntegerDataType::INT2);
        }
    }
}

#[derive(Default, Debug)]
pub struct ERRPacket {
    inner: NativePacketPayload,

    // 0xFF
    // int<1>
    header: Int1,
    // int<2>
    error_code: Int2,

    // string[1]
    // # marker of the SQL state
    sql_state_marker: Option<String>,
    // string[5]
    sql_state: Option<String>,

    // string<EOF>
    error_message: String,
}

// ER_NET_READ_INTERRUPTED 08S01
impl ERRPacket {
    pub fn new(inner: NativePacketPayload) -> Self {
        ERRPacket {
            inner,
            header: 0xFF,
            error_code: 0,
            sql_state_marker: None,
            sql_state: None,
            error_message: String::new(),
        }
    }

    pub fn handle(&mut self, capability_flags: usize) {
        // from => NativePacketPayload::leader()
        // self.header = self.inner.read_int(crate::nativs::IntegerDataType::INT1);
        self.error_code = self.inner.read_int(crate::nativs::IntegerDataType::INT2);

        if (capability_flags & CLIENT_PROTOCOL_41) != 0 {
            self.sql_state_marker = Some(self.inner.read_fix_str(1));
            self.sql_state = Some(self.inner.read_fix_str(5));
        }
        self.error_message =
            String::from_utf8_lossy(&self.inner.read_bytes(StringSelfDataType::STRING_EOF))
                .to_string();
    }
}

#[derive(Default, Debug)]
// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_auth_switch_request.html
pub struct SwitchRequestPacket {
    inner: NativePacketPayload,

    // 0xFE (254)
    // int<1>
    status_tag: Int1,

    // string[NUL]
    pub plugin_name: String,

    // string[EOF]
    plugin_provided_data: String,
}

impl SwitchRequestPacket {
    pub fn new(inner: NativePacketPayload) -> Self {
        SwitchRequestPacket {
            inner,
            status_tag: 0xFE,
            plugin_name: String::new(),
            plugin_provided_data: String::new(),
        }
    }

    pub fn handle(&mut self) {
        self.plugin_name =
            String::from_utf8_lossy(&self.inner.read_bytes(StringSelfDataType::STRING_TERM))
                .to_string();
        self.plugin_provided_data =
            String::from_utf8_lossy(&self.inner.read_bytes(StringSelfDataType::STRING_EOF))
                .to_string();
    }
}


pub fn sha2_password(db_user_password: &str, salt: &str) -> Vec<u8> {
    use sha256::digest;

    /* SHA2(src) => digest_stage1 */
    let digest_stage1 = digest(db_user_password);
    /* SHA2(digest_stage1) => digest_stage2 */
    let digest_stage2 = digest(&digest_stage1);
    /* SHA2(digest_stage2, salt) => scramble_stage1 */
    let scramble_stage1 = digest(format!("{digest_stage2}{salt}"));
    /* XOR(digest_stage1, scramble_stage1) => scramble */
    println!("{scramble_stage1}");
    let digest_stage1 = digest_stage1.as_bytes();
    let scramble_stage1 = scramble_stage1.as_bytes();

    let mut scramble = Vec::new();
    for (i, n) in scramble_stage1.iter().enumerate() {
        scramble.push(digest_stage1[i] ^ n);
    }

    scramble
}
